﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace iNoteIt.Plugin
{
    public class PluginCollection : IEnumerable<PluginMetadata>
    {
        private class PluginInst
        {
            public bool Active { get; set; }
            public IPlugin Instance { get; set; }
        }

        private readonly List<PluginInst> _installedPlugins;
        private readonly Dictionary<Guid, PluginInst> _idToPluginDic;

        public PluginCollection()
        {
            _installedPlugins = new List<PluginInst>();
            _idToPluginDic = new Dictionary<Guid, PluginInst>();
        }

        public PluginMetadata Add(string pluginPath)
        {
            //load plugin.
            var assembly = Assembly.LoadFile(pluginPath);

            //Get entry plugin type.
            Type entryType = null;

            foreach (var exportedType in assembly.GetExportedTypes())
            {
                foreach (var @interface in exportedType.GetInterfaces())
                {
                    if (@interface == typeof(IPlugin))
                    {
                        entryType = exportedType;
                        break;
                    }
                }

                if (entryType != null)
                    break;
            }

            if (entryType == null)
                throw new FileNotSupportException();

            //Create an instance for EntryType.
            IPlugin inst;
            try
            {
                inst = Activator.CreateInstance(entryType) as IPlugin;
            }
            catch (Exception ex)
            {
                throw new PluginInnerException(ex);
            }

            //var scope = new PluginLoadScope();

            try
            {
                if (inst == null) throw new PluginInnerException(null);
                //stop loading plugin if the plugin has already been loadded.
                if (_idToPluginDic.ContainsKey(inst.Metadata.Identity))
                    return _idToPluginDic[inst.Metadata.Identity].Instance.Metadata;

                //revise: plugin will not be loaded automatically after added to collection.
                //inst.Load(scope);
            }
            catch (Exception ex)
            {
                throw new PluginInnerException(ex);
            }

            _installedPlugins.Add(new PluginInst()
            {
                Active = false,
                Instance = inst
            });
            _idToPluginDic.Add(inst.Metadata.Identity,new PluginInst()
            {
                Active = false,
                Instance = inst
            });

            return inst.Metadata;
        }

        public void Load(Guid pluginId)
        {
            var plugin = _idToPluginDic[pluginId];

            if (!plugin.Active)
            {
                var scope = new PluginLoadScope();

                plugin.Instance.Load(scope);
                plugin.Active = true;
            }
        }

        public void Unload(Guid pluginId)
        {
            var plugin = _idToPluginDic[pluginId];

            if (!plugin.Active)
            {
                var scope = new PluginUnloadScope();

                plugin.Instance.Unload(scope);
                plugin.Active = false;
            }
        }

        public bool IsPluginLoaded(Guid pluginId)
        {
            if (_idToPluginDic.ContainsKey(pluginId))
            {
                return _idToPluginDic[pluginId].Active;
            }
            else
            {
                throw new KeyNotFoundException();
            }
        }

        public PluginMetadata this[Guid pluginId] => _idToPluginDic[pluginId].Instance.Metadata;

        public PluginMetadata this[int index] => _installedPlugins[index].Instance.Metadata;

        public int Count => _installedPlugins.Count;

        public IEnumerator<PluginMetadata> GetEnumerator()
        {
            return new Enumerator(_installedPlugins);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        ~PluginCollection()
        {
            var scope=new PluginUnloadScope();

            foreach (var installedPlugin in _installedPlugins)
            {
                if (installedPlugin.Active)
                {
                    installedPlugin.Instance.Unload(scope);
                    //installedPlugin.Active = false;
                }
            }
        }

        #region Enumerator

        private struct Enumerator : IEnumerator<PluginMetadata>
        {
            private readonly List<PluginInst> _list;
            private int _offset;

            internal Enumerator(List<PluginInst> list)
            {
                _list = list;
                _offset = -1;
            }

            public PluginMetadata Current => _list[_offset].Instance.Metadata;

            object IEnumerator.Current => _list[_offset].Instance.Metadata;

            public void Dispose()
            {
               //nothing
            }

            public bool MoveNext()
            {
                _offset++;
                return _offset < _list.Count;
            }

            void IEnumerator.Reset()
            {
                _offset = -1;
            }
        }

        #endregion
    }
}
